#include "tabu.h"

tabu::tabu(    DataBase *_base,
                             int _iter,
                             int _iterImprov,
                             int _improv,
                             int _tabuLength,
                             int _iterJump,
                             int _iterLocalJump,
                             QVector<int> _firstSolution,
                             int _kara,
                             int _nagroda){

    base=_base;
    //depending on base:
    deadline=base->getDeadline();
    quantity=base->getAmount();
    products=base->countProducts();
    productionPerDay=base->getQuantityAvalaiblePerDay();
    //depending on search parameters
    iter = _iter;
    iterImprov=_iterImprov;
    tabuLength=_tabuLength;
    improv=_improv;    
    iterJump=_iterJump;
    iterLocalJump=_iterLocalJump;
    firstSolution=_firstSolution;
    kara=_kara;
    nagroda=_nagroda;
}

tabu::tabu(){
    deadline=-1;
    quantity=0;
    products=0;
    productionPerDay=0;
    iter = 0;
    iterImprov=0;
    improv=0;
    iterJump=0;
    kara=0;
    nagroda=0;
}


void tabu::ConductSearch(){
    QVector<int> tempSolution;       //actual solution found
    QVector<int> centerSolution;     //searching around it's neighbourhood
    QVector<int> actualBestSolution; //next solution to be searched around its neighbourhood
    int iterations=0;
    int localIterations=0;  // iterations around proper neighbourhood
    int improvement=100;        //at the begining assuming 100% improvement


    numberOfCorrectSolutions=1;
    tempSolution=FindFirstSolution();
    bestSolution=tempSolution;  //value to comparison needed,
    centerSolution=tempSolution;
    AddSolution(tempSolution);  //adds to values, solutions


    while (StoppingCondition(iterations, improvement)){
        //tabu searching...  condition for global search
        if(JumpCondition(improvement)){ //if true jump and generate random solution
            centerSolution=GenerateSolution();
            AddSolution(centerSolution);

            localIterations=0;
            tabuList.clear();
        }
        else{   //find solution in neighbourhood
            tempSolution=GenerateNeighbourhood(centerSolution); //includes tabu list

            if(localIterations==0)
                actualBestSolution=tempSolution;
            else if(Assess(tempSolution)<Assess(actualBestSolution))
                actualBestSolution=tempSolution;

            if (Assess(actualBestSolution)<BestValue(numberOfCorrectSolutions)){
                bestSolution=tempSolution;
                centerSolution=tempSolution;
                localIterations=0;
                improvement=(BestValue(numberOfCorrectSolutions)-(Assess(actualBestSolution))*100/BestValue(numberOfCorrectSolutions));

            }
            else{
                localIterations++;
                improvement=0;
            }

            AddSolution(tempSolution);
        }

        if(localIterations==iterLocalJump){
            localIterations=0;
            centerSolution=tempSolution;
        }

        //increment iterations
        iterations++;
      }
}

int tabu::NumberOfCorrectSolutions(){
    return numberOfCorrectSolutions;
}

float tabu::BestValue(int correctSolutionFound){
    float bestValue=values[0];
    for(int i=1; i<correctSolutionFound; i++){
        if(bestValue>values[i])
            bestValue=values[i];
    }
    return bestValue;
}

 QVector<int> tabu::BestSolution(){
    QVector<int> solutionDays;
    int days=TotalProductionDelay(bestSolution)+DaysAmount(1.0, productionPerDay);
    solutionDays=bestSolution;
    solutionDays.append(days);

    return solutionDays;
}

QVector<QVector<int> > tabu::Solutions(){
    QVector<QVector<int> > outSolutions;
    for(int i=0; i<numberOfCorrectSolutions; i++){
        int days=TotalProductionDelay(solutions[i])+DaysAmount(1.0, productionPerDay);
        outSolutions[i]=solutions[i];
        outSolutions[i].append(days);
    }

    return outSolutions;
}

QVector<float> tabu::Values(){
    return values;
}

bool tabu::StoppingCondition(int iterations, int improvement){// '1' kontynuuj, 0 przerwij
    static int timeSinceLastImprovement=0;
    if(improv==0){                                  //condition for continue when only iterations matters
        if(iterations<iter) return true;
        else return false;
    }
    else if(iter==0){                               //condition for continue when only improvement matters
        if(improvement<improv)
            timeSinceLastImprovement++;
        else
            timeSinceLastImprovement=0;

        if(timeSinceLastImprovement<iterImprov)
            return true;
        else
            return false;
    }
    else{                                           //condition for continue when both iterations and improvement matters
            if(iterations<iter){
                if(improvement<improv)
                    timeSinceLastImprovement++;
                else
                    timeSinceLastImprovement=0;

                if(timeSinceLastImprovement<iterImprov)
                    return true;
                else
                    return false;
            }
            else return false;
    }

}

bool tabu::JumpCondition(int improvement){
    static int localIterations;
    if (iterJump==0) return false;

    else{
        if(improvement==0)
            localIterations++;
        else
            localIterations=0;
    }
    if (localIterations==iterJump){
        localIterations=0;
        return true;
    }
    else
        return false;



}

void tabu::AddSolution(QVector<int> tempSolution){
    float tempValue=Assess(tempSolution);

    solutions.append(tempSolution);
    values.append(tempValue);
    numberOfCorrectSolutions++;


}

void tabu::AddTabuMove(int product, int supplier){
    QVector<int> tabuMove;
    tabuMove.append(product);
    tabuMove.append(supplier);
    tabuList.append(tabuMove);

    if (tabuList.size()==tabuLength)
            tabuList.erase(0);
}

bool tabu::CheckTabu(int product, int supplier){ //true means move is tabu
    QVector<int> move;
    move.append(product);
    move.append(supplier);
    for(int i=0; i<tabuList.size(); i++)
        if (tabuList[i]==move)
            return true;

    return false;
}

QVector<int> tabu::FindFirstSolution(){
    if (firstSolution[0]!=-1)                   //if first solution is not random
        return firstSolution;
    else                                        //otherwise
        return GenerateSolution();

}

QVector<int> tabu::GenerateSolution(){
    QVector<int> randomSolution;
    for(int i=0; i<products; i++){          //for every product generating random supplier
        randomSolution.append(qrand()%((base->getProduct(i).countSuppliers())-0) + 0);        //random supplier between 0 and supplier amount
    }
    return randomSolution;
}

QVector<int> tabu::GenerateNeighbourhood(QVector<int> centerSolution){
    QVector<int> neighbourhood;
    int productSupplierMove= qrand()%((products+1)-0)+0;
    int supplierMove=qrand()%((base->getProduct(productSupplierMove).countSuppliers())-0) + 0;

    while (CheckTabu(productSupplierMove,supplierMove)){//if true move is on tabu list
        productSupplierMove= qrand()%((products+1)-0)+0;
        supplierMove=qrand()%((base->getProduct(productSupplierMove).countSuppliers())-0) + 0;
    }

    centerSolution.replace(productSupplierMove, supplierMove);
    AddTabuMove(productSupplierMove, supplierMove);
    return centerSolution; //solution in neighbourhood of centerSolution
}


float tabu::Assess(QVector<int> tempSolution){
    float result;
    int days=TotalProductionDelay(tempSolution)+DaysAmount(1.0, productionPerDay);
    for(int i=0; i<products; i++){
        //ilosc dostaw*sztuki w dostawie*cena sztuki*
        result+=DaysAmount(base->getProduct(i).getQuantity(),base->getProduct(i).getSupplier(tempSolution[i]).getQuantityAvalaiblePerDay())
                *base->getProduct(i).getSupplier(tempSolution[i]).getQuantityAvalaiblePerDay()
                *base->getProduct(i).getSupplier(tempSolution[i]).getCost();
    }

    if(days<=deadline)
        result-=nagroda*(deadline-days);

    result+=float(kara*(days-deadline));
    return result;
}

int tabu::TotalProductionDelay(QVector<int> tempSolution){
    int delay=0;
    int tempDelay=0;
    for(int i=0; i< products; i++){
        tempDelay=ItemDelay(base->getProduct(i).getQuantity(),
                            base->getProduct(i).getSupplier(tempSolution[i]).getProductionTime(),
                            base->getProduct(i).getSupplier(tempSolution[i]).getQuantityAvalaiblePerDay());
        if(delay<tempDelay)
            delay=tempDelay;
    }
    return delay;
}

int tabu::ItemDelay(int quantityPerItem,  int providerDelay, int quantityAvailablePerDay){
    //ilosci dni dostaw-ilosc dni produkcji
    int i=DaysAmount(quantityPerItem, quantityAvailablePerDay)-DaysAmount(1.0, productionPerDay);
    if(i<0)
        i=0;                  //gdy jedna (duza)dostawa i kilka dni produkcji
    return i+providerDelay;    //opoznienie produkcji wzgledem dostaw + opoznienie bezwzgledne dostaw(pierwszej)
}

int tabu::DaysAmount(float quantityPerItem, int quantityAvailablePerDay){

    int days=quantity*quantityPerItem/quantityAvailablePerDay;    //ilosc dni z ucieta koncowka(float->int)

    if(float(days)<float(quantity*quantityPerItem)/float(quantityAvailablePerDay))     //warunkowe dodanie dnia jezeli koncowka >0)
        days+=1;

    return days;
}


